﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using OpenCLNet;
using CL = OpenCLNet;

namespace Shader
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        #region OpenCL代码
        private static string CLCode = @"
__kernel void vector_add_gpu(__global int* src_a, __global int* src_b, __global int* res)
{
	const int idx = get_global_id(0);
    for (int i = 0; i < src_a[idx]; i++)
    {  
        res[idx] = src_b[idx];
    }  
}

__kernel void vector_inc_gpu(__global int* src_a, __global int* res)
{
	const int idx = get_global_id(0);
	res[idx] =src_a[idx] + 1;
}
";//运行在GPU的代码
        #endregion

        public MainWindow()
        {
            //GUP使用结论1：GPU中可以运算8位数（10000000）以内的数字计算而且结果不能超过10000000，具体不能超过多少有待测试，
            //GUP使用结论2：GPU调用运算快，但是定义内存速度慢，也就是用于GPU计算的数组长度要小，写到GPU中的代码运算量可以很大
            //GUP使用结论3：总结论1、2可以得出当用于GPU计算的数组长度小（但也不能太小具体多少有待研究），GPU中代码运算量大使用GPU可以远远优于CPU，本示例CPU用时约计600s，GPU约计5s
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //选中一个设备==================================================================================
            //获取平台数量
            OpenCL.GetPlatformIDs(32, new IntPtr[32], out uint num_platforms);
            var devs = new List<Device>();
            //枚举所有平台下面的设备(CPU和GPU)
            for (int i = 0; i < num_platforms; i++)
            {
                //这里后面有个参数,是Enum,这里选择GPU,表示只枚举GPU,在没有GPU的电脑上可以选CPU,也可以传ALL,会把所有设备枚举出来供选择
                devs.AddRange(OpenCL.GetPlatform(i).QueryDevices(DeviceType.GPU));
            }
            //选中运算设备,这里选第一个其它的释放掉
            var oclDevice = devs[0];//选中一个设备




            //配置上下文====================================================
            //根据配置建立上下文
            var oclContext = oclDevice.Platform.CreateContext(
                new[] { (IntPtr)ContextProperties.PLATFORM, oclDevice.Platform.PlatformID, IntPtr.Zero, IntPtr.Zero },
                new[] { oclDevice },
                (errInfo, privateInfo, cb, userData) => { },
                IntPtr.Zero
            );//配置上下文






            //创建命令队列====================================================
            var oclCQ = oclContext.CreateCommandQueue(oclDevice, CommandQueueProperties.PROFILING_ENABLE);//创建命令队列




            //编译OpenCL代码,并引出两个Kernel===============================================================================
            //定义一个字典用来存放所有核
            var Kernels = new Dictionary<string, Kernel>();
            #region 编译代码并导出核
            var oclProgram = oclContext.CreateProgramWithSource(CLCode);
            try
            {
                oclProgram.Build();
            }
            catch (OpenCLBuildException EEE)
            {
                Console.WriteLine(EEE.BuildLogs[0]);
                Console.ReadKey(true);
                throw EEE;
                //return null;
            }
            foreach (var item in new[] { "vector_add_gpu", "vector_inc_gpu" })
            {
                Kernels.Add(item, oclProgram.CreateKernel(item));
            }
            oclProgram.Dispose();
            #endregion







            //调用Kernel示例===============================================================================
            #region 调用vector_add_gpu核
            {
                int count = 400;
                var A = new int[count];
                var B = new int[count];
                var C = new int[count];
                for (int i = 0; i < count; i++)
                {
                    A[i] = 1000000000;
                    B[i] = 2;
                }
                for (int i = 0; i < A.Length; i++)
                {
                    for (int j = 0; j < A[i]; j++)
                    {
                        C[i] = B[i];
                    }
                }
                //在显存创建缓冲区并把HOST的数据拷贝过去
                Mem n1 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, A.Length * sizeof(int), A.ToIntPtr());
                var n2 = oclContext.CreateBuffer(MemFlags.READ_WRITE | MemFlags.COPY_HOST_PTR, B.Length * sizeof(int), B.ToIntPtr());
                //还有一个缓冲区用来接收回参
                var n3 = oclContext.CreateBuffer(MemFlags.READ_WRITE, B.Length * sizeof(int), IntPtr.Zero);


                //把参数填进Kernel里
                Kernels["vector_add_gpu"].SetArg(0, n1);
                Kernels["vector_add_gpu"].SetArg(1, n2);
                Kernels["vector_add_gpu"].SetArg(2, n3);
                //把调用请求添加到队列里,参数分别是:Kernel,数据的维度,每个维度的全局工作项ID偏移,每个维度工作项数量(我们这里有4个数据,所以设为4),每个维度的工作组长度(这里设为每4个一组)
                oclCQ.EnqueueNDRangeKernel(Kernels["vector_add_gpu"], 1, new[] { 0 }, new[] { count }, new[] { 4 });
                //设置栅栏强制要求上面的命令执行完才继续下面的命令.
                oclCQ.EnqueueBarrier();
                //添加一个读取数据命令到队列里,用来读取运算结果
                oclCQ.EnqueueReadBuffer(n3, true, 0, C.Length * sizeof(int), C.ToIntPtr());
                //开始执行
                oclCQ.Finish();

                n1.Dispose();
                n2.Dispose();
                n3.Dispose();
                C = C;//在这里打断点,查看返回值
            }
            // */
            #endregion







            //释放资源=================================================================================================
            //按顺序释放之前构造的对象
            oclCQ.Dispose();
            oclContext.Dispose();
            oclDevice.Dispose();




        }
    }
}
